﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;
using System.Globalization;
using AnyGrid.Metadata;
using AnyGrid.Properties;
using System.Web.Script.Serialization;

namespace AnyGrid.Renderers.JqGrid
{
    internal class JqGridRenderer : BaseGridRenderer
    {
        private JqGridRendererOptions _options;

        /// <summary>
        /// Allows tweaking of code generation for grid. See JqGridRendererOptions for details on 
        /// individual options. In Global.asax.cs, you can set via GridRenderers.Renderers.OfType&lt;JqGridRenderer&gt;().Single().Options
        /// </summary>
        public JqGridRendererOptions Options 
        {
            get
            {
                if (_options == null)
                {
                    _options = new JqGridRendererOptions();
                }
                return _options;
            }
            set
            {
                _options = value;
            }
        }

        protected override string RenderGridJavaScript(GridViewModel grid)
        {
            if (grid == null)
            {
                throw new ArgumentNullException("grid");
            }
            var baseResult = base.RenderGridJavaScript(grid) ?? "";
            return baseResult + JqGrid(grid);
        }

        public override string RenderHtml(GridViewModel grid, IDictionary<string, object> htmlAttributes)
        {
            if (grid == null)
            {
                throw new ArgumentNullException("grid");
            }
            var baseHtml = base.RenderHtml(grid, htmlAttributes);
            TagBuilder tagBuilder = TableTagBuilder(grid, htmlAttributes);
            string id;
            if (!tagBuilder.Attributes.TryGetValue("id", out id))
            {
                throw new InvalidOperationException(Resources.GridTableIdIsRequired);
            }
            // search div
            string searchHtml;
            if (grid.GridContext.ViewOptions.Search)
            {
                TagBuilder searchTagBuilder = new TagBuilder("div");
                searchTagBuilder.GenerateId(grid.GridContext.ViewOptions.SearchHtmlId);
                AppendClass(searchTagBuilder, "anyGridSearch");
                searchHtml = searchTagBuilder.ToString(TagRenderMode.Normal);
            }
            else
            {
                searchHtml = string.Empty;
            }
            // grid table
            var gridHtml = tagBuilder.ToString(TagRenderMode.Normal);
            // pager div
            string pagerHtml;
            if (grid.GridContext.ViewOptions.Pager)
            {
                TagBuilder pagerTagBuilder = new TagBuilder("div");
                pagerTagBuilder.GenerateId(grid.GridContext.ViewOptions.PagerHtmlId);
                pagerHtml = pagerTagBuilder.ToString(TagRenderMode.Normal);
            }
            else
            {
                pagerHtml = string.Empty;
            }
            return baseHtml + searchHtml + gridHtml + pagerHtml;
        }

        /// <summary>
        /// Built-in grid formatters from grid source code
        /// </summary>
        private static string[] _builtInFormatters = new[] { "defaultFormat", "email", "checkbox", "link", "showlink", "integer", "number", "currency", "date", "select", "rowactions", "actions" };

        private static void AddFormatterIfNeeded(JavaScriptObject gridColumn, ModelMetadata columnPropertyModelMetadata, GridViewModel model)
        {
            if (model == null)
            {
                throw new ArgumentNullException("model");
            }
            var columnTemplate = columnPropertyModelMetadata.TemplateHint;
            if (string.IsNullOrEmpty(columnTemplate))
            {
                var modelType = Nullable.GetUnderlyingType(columnPropertyModelMetadata.ModelType) ?? columnPropertyModelMetadata.ModelType;
                columnTemplate = modelType.Name;
            }

            AddFormatterForColumnTemplate(gridColumn, columnTemplate, columnPropertyModelMetadata.TemplateHint);
            AddFormatOptionsIfNeeded(gridColumn, columnPropertyModelMetadata, model);
        }

        public static void AddFormatterForColumnTemplate(JavaScriptObject gridColumn, string columnTemplate, string templateHint)
        {
            if (gridColumn == null)
            {
                throw new ArgumentNullException("gridColumn");
            }
            if (string.IsNullOrEmpty(columnTemplate))
            {
                return;
            }
            if (columnTemplate.Equals("boolean", StringComparison.OrdinalIgnoreCase))
            {
                gridColumn.Add("formatter", new JavaScriptLiteral("checkbox"));
            }
            else if (_builtInFormatters.Contains(columnTemplate))
            {
                // built-in formatters are passed as strings
                gridColumn.Add("formatter", new JavaScriptLiteral(columnTemplate));
            }
            else
            {
                // built-in formatter not found. Don't specify a formatter unless user forces it.
                if (!string.IsNullOrEmpty(templateHint))
                {
                    gridColumn.Add("formatter", new JavaScriptMethodReference(columnTemplate));
                }
            }
        }

        private static void AddFormatOptionsIfNeeded(JavaScriptObject gridColumn, ModelMetadata columnPropertyModelMetadata, GridViewModel model)
        {
            object additionalValue;
            ColumnOptionCollection options;
            object formatoptions = null;
            if (columnPropertyModelMetadata.AdditionalValues.TryGetValue(AdditionalValuesKeys.GridOptions, out additionalValue))
            {
                options = additionalValue as ColumnOptionCollection;
                if (options != null)
                {
                    formatoptions = options.Values("formatoptions", GridKind.JqGrid).Single();
                }
            }
            if (model.GridContext.ViewOptions.ColumnOptions != null)
            {
                ColumnOptionCollection fromViewOptions = null;
                if (model.GridContext.ViewOptions.ColumnOptions.TryGetValue(columnPropertyModelMetadata.PropertyName, out fromViewOptions))
                {
                    var fromViewFormatOptions = fromViewOptions.Values("formatoptions", GridKind.JqGrid);
                    if (fromViewFormatOptions.Any())
                    {
                        formatoptions = fromViewFormatOptions.Single();
                    }
                }
            }
            if (formatoptions != null)
            {
                gridColumn.Add("formatoptions", JavaScriptElement.Create(formatoptions));
            }
        }

        private JavaScriptArray ColModel(GridViewModel model)
        {
            var rowModelMetadata = model.GridContext.RowModelMetadata;
            // see http://www.trirand.com/jqgridwiki/doku.php?id=wiki:colmodel_options for possible options
            var result = new JavaScriptArray();
            foreach (var property in rowModelMetadata.Properties)
            {
                var column = new JavaScriptObject();
                column.Add("name", new JavaScriptLiteral(property.PropertyName));
                column.Add("index", new JavaScriptLiteral(property.PropertyName));
                if (!property.ShowForDisplay)
                {
                    column.Add("hidden", new JavaScriptLiteral(true));
                }
                AddFormatterIfNeeded(column, property, model);
                if (property.AdditionalValues.ContainsKey(AdditionalValuesKeys.MaximumStringLength))
                {
                    int len = (int)property.AdditionalValues[AdditionalValuesKeys.MaximumStringLength];
                    if (len > 0)
                    {
                        column.Add("width", new JavaScriptLiteral(len * Options.PixelsPerCharacter));
                    }
                }
                if (property.AdditionalValues.ContainsKey(AdditionalValuesKeys.DisableSort))
                {
                    bool disabled = (bool)property.AdditionalValues[AdditionalValuesKeys.DisableSort];
                    if (disabled)
                    {
                        column.Add("sortable", new JavaScriptLiteral(false));
                    }
                }
                result.Add(column);
            }
            return result;
        }

        private static JavaScriptArray ColNames(ModelMetadata rowModelMetadata)
        {
            var names = from property in rowModelMetadata.Properties
                        select new JavaScriptLiteral(property.ShortDisplayName ?? property.GetDisplayName());
            return new JavaScriptArray(names);
        }

        private string JqGrid(GridViewModel grid)
        {
            return string.Format(CultureInfo.InvariantCulture, 
                "jQuery('#{0}').{1}({2});{3}", 
                grid.GridContext.ViewOptions.GridHtmlId, Options.JqGridMethod, OptionsForGrid(grid), Environment.NewLine);
        }

        private string OptionsForGrid(GridViewModel model)
        {
            // see http://www.trirand.com/jqgridwiki/doku.php?id=wiki:options for grid options
            var result = new JavaScriptObject();
            result.Add("url", new JavaScriptLiteral(model.GridContext.DataUrl));
            result.Add("colModel", ColModel(model));
            result.Add("colNames", ColNames(model.GridContext.RowModelMetadata));
            if (model.AllowedOperations != null)
            {
                var anyGridOpts = new JavaScriptObject();
                var ao = new JavaScriptObject();
                ao.Add("deleteActionName", new JavaScriptLiteral(model.AllowedOperations.DeleteActionName));
                ao.Add("detailsActionName", new JavaScriptLiteral(model.AllowedOperations.DetailsActionName));
                ao.Add("insertActionName", new JavaScriptLiteral(model.AllowedOperations.InsertActionName));
                ao.Add("updateActionName", new JavaScriptLiteral(model.AllowedOperations.UpdateActionName));
                anyGridOpts.Add("allowedOperations", ao);
                result.Add("anyGrid", anyGridOpts);
            }
            if (model.FirstPageData != null)
            {
                AddData(result, model);
            }
            if (model.GridContext.ViewOptions.Multiselect)
            {
                result.Add("multiselect", new JavaScriptLiteral(true));
            }
            if (model.GridContext.ViewOptions.Pager)
            {
                result.Add("pager", new JavaScriptLiteral("#" + model.GridContext.ViewOptions.PagerHtmlId));
            }
            SetSortNameAndOrder(model.GridContext.RowModelMetadata, result);
            return result.ToString();
        }

        private static void AddData(JavaScriptObject gridOptions, GridViewModel model)
        {
            if (gridOptions == null)
            {
                throw new ArgumentNullException("gridOptions");
            }
            if (model.FirstPageData == null)
            {
                return;
            }
            var recordCount = model.FirstPageData.Cast<object>().Count();
            var jqGridData = new JqGridData
            {
                Page = 1,
                Records = recordCount,
                Rows = model.FirstPageData,
                Total = 1
            };
            var jsonData = new JavaScriptSerializer().Serialize(jqGridData);
            gridOptions.Add("datatype", new JavaScriptLiteral("jsonstring"));
            gridOptions.Add("datastr", new JavaScriptLiteral(jsonData));
        }

        private static void SetSortNameAndOrder(ModelMetadata rowModelMetadata, JavaScriptObject options)
        {
            if (rowModelMetadata.AdditionalValues.ContainsKey(AdditionalValuesKeys.SortColumnName))
            {
                var descending = rowModelMetadata.AdditionalValues.ContainsKey(AdditionalValuesKeys.SortDescending) ?
                    (bool)rowModelMetadata.AdditionalValues[AdditionalValuesKeys.SortDescending] : false;
                options.Add("sortname", new JavaScriptLiteral((string)rowModelMetadata.AdditionalValues[AdditionalValuesKeys.SortColumnName]));
                if (descending)
                {
                    options.Add("sortorder", new JavaScriptLiteral("desc"));
                }
            }
            else
            {
                // return first visible column.
                var sn = (from p in rowModelMetadata.Properties
                          where p.ShowForDisplay
                          select p.PropertyName).FirstOrDefault();
                if (string.IsNullOrEmpty(sn))
                {
                    throw new InvalidOperationException(Resources.SortNameUnspecified);
                }
                options.Add("sortname", new JavaScriptLiteral(sn));
            }
        }
    }
}
